跳到主要内容

AI 产品体验

副标题:流式交互、中断续写、失败兜底与可解释

目标:把“不确定的 AI 输出”包装成“稳定、好用、可控”的用户体验。这也是前端在 AI 产品里最稀缺的价值。

目录

体验为什么决定胜负

很多 AI 产品“能力差”,本质不是模型差,而是体验没兜住:

  • 等半天没反应,用户以为卡死
  • 一直输出,用户停不下来
  • 输出一半报错,用户不知道怎么办
  • 答案看起来对,但无法验证来源

前端 + AI 的核心价值是:把模型能力转化为可感知、可控制、可恢复的交互。

流式输出:打字机效果只是起点

流式输出要解决的不是“显示”,而是“控制”

至少要覆盖:

  • 实时反馈:首 token 时间(TTFT)越低越好
  • 节流渲染:避免每个 token 都触发重渲染
  • 增量渲染策略
    • 文本:直接增量拼接 + 节流刷新
    • Markdown:建议“流式阶段先纯文本”,结束后再一次性 Markdown 渲染(或分段渲染)

体验细节(非常加分)

  • 显示“正在思考/正在检索/正在生成”的阶段提示(尤其 RAG)
  • 显示“已生成字数/预计剩余”(可选)
  • 支持“复制本条回答”“复制引用来源”

示例:前端节流渲染(避免每个 token 触发渲染)

let buffer = "";
let lastFlush = 0;
const FLUSH_INTERVAL = 80; // ms

function onDelta(text: string) {
buffer += text;
const now = Date.now();
if (now - lastFlush > FLUSH_INTERVAL) {
flush();
lastFlush = now;
}
}

function flush() {
if (!buffer) return;
// 这里替换成 setState/emit 逻辑
appendToUI(buffer);
buffer = "";
}

经验值:50-120ms 的刷新间隔体感最好,能兼顾“像打字”与“不卡顿”。

推理模型的体验:把“漫长等待”变成“看得见的思考”

接入推理模型(o 系列 / R1 / thinking 模式)时,首字延迟(TTFT)可能长达数秒甚至几十秒,因为模型在“思考”。如果只显示一个转圈,用户会以为卡死。

体验设计要点:

  • 思考阶段可视化:显示“正在思考 / 正在分析 / 正在规划”,有条件就展示思考摘要(多数厂商只给摘要,不给完整原始思维链)。
  • 思考过程可折叠:做一个默认收起的“思考过程”面板(类似 ChatGPT/Claude 的 reasoning 折叠区),想看的人能展开。
  • 分阶段流式:先流“思考摘要”,再流“正式答案”,两段视觉上区分开。
  • 成本与时延预期:对耗时长的推理任务,给“预计需要更久”的提示,降低焦虑。

这一段是“懂推理模型”的体验细节,面试里能体现你不是只会接普通 chat。

中断 / 继续 / 重试:三个按钮背后的状态机

一个实用的聊天消息状态机(建议你按这个设计 UI/数据结构):

  • idle:未开始
  • streaming:正在流式输出
  • stopped:用户主动停止
  • failed:失败(超时/限流/网络/模型错误)
  • done:完成

对应用户操作:

  • 停止:streaming → stopped
  • 继续生成:stopped → streaming(携带“已生成内容”作为上下文继续)
  • 重试:failed → streaming(可带上同一输入、不同策略)

工程要点:

  • 停止一定要“真的停止”:
    • 前端中止请求
    • 服务端也要尽快中止对模型的流式拉取(避免继续烧 Token)
  • 并发控制:
    • 同一会话一次只允许一个 streaming(或给用户明确提示)

图示:消息状态机(Mermaid)

并发与会话策略(一定要写清楚)

  • 同会话单流式:避免“一问多答”的 UI 混乱
  • 跨会话并发上限:例如每用户最多 2 条流式并发
  • 停止/继续的上下文策略
    • 停止时保留已生成内容
    • 继续时把“已生成内容 + 原问题”一起作为上下文

失败兜底:超时、限流、内容不合规、模型不可用

1)超时

用户视角:不要只显示“错误”,要告诉他能做什么:

  • “已生成内容保留”
  • “你可以:继续生成 / 重新生成 / 缩短问题 / 稍后再试”

UI 文案示例(可直接用)

  • 标题:生成超时
  • 正文:已保留当前内容。你可以点击“继续生成”,或缩短问题再试。

2)限流/配额不足

这是 AI 产品最常见失败之一,建议在 UI 上做出“可理解”的提示:

  • 是“并发太多”还是“今日额度用完”
  • 引导升级/稍后再试/减少输出长度

UI 文案示例

  • 标题:请求过于频繁
  • 正文:当前并发已满,请稍后重试,或先结束其他对话。

3)内容不合规

如果你接入的是有内容安全策略的模型:

  • 明确告诉用户“哪些部分不支持”
  • 给出可替代建议(比如“我可以改成科普/非敏感表达”)

UI 文案示例

  • 标题:内容不符合使用规范
  • 正文:我无法处理该请求,但可以帮你改为科普/非敏感的表达方式。

4)模型不可用

做“多模型降级”:

  • 主模型失败 → 备用模型(能力可能弱,但可用)
  • UI 显示“已自动切换模型”(透明且可控)

UI 文案示例

  • 标题:模型暂不可用
  • 正文:已自动切换到备用模型,结果可能略有差异。

可解释与可追溯:引用来源、证据片段与操作日志

对用户来说,“答案对不对”有时很难判断;但“证据从哪来”更容易判断。

你可以在 UI 上提供:

  • 引用来源:每段回答对应哪些文档片段
  • 证据片段:可展开查看 chunk 内容
  • 定位到原文:如果有页码/段落定位就更好

对工程来说(排障与审计):

  • 每次回答都带 traceId
  • 记录:检索到的 docId/chunkId、模型参数、耗时、错误码(脱敏)

透明化设计:让用户“看到过程”

  • 检索型回答展示“证据来源列表”
  • 工具型回答展示“执行步骤(可折叠)”
  • 失败时显示“错误码 + traceId”,方便反馈

Generative UI(生成式界面):让 AI 输出“可交互组件”而非纯文本

2024 年后前端 AI 的一个重要趋势:不再只把模型输出渲染成文字/Markdown,而是根据模型的结构化输出/工具调用,动态渲染出真正的 React 组件——天气卡片、订单表格、可点击的确认按钮、图表等。

核心思路:

  • 模型负责“决定展示什么 + 提供结构化数据”(通过工具调用或结构化输出),前端负责“把数据映射成组件”
  • 例如模型调用 getWeather 工具 → 前端不显示 JSON,而是渲染一个 <WeatherCard>
  • Vercel AI SDK 时,useChat 的 message parts 里就带有 tool-xxx 类型的 part,可据此渲染对应组件。
// 根据消息 part 类型渲染不同 UI(Generative UI 思路)
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <MarkdownView key={i} content={part.text} />;
case "tool-getWeather":
return part.state === "output-available"
? <WeatherCard key={i} data={part.output} />
: <Skeleton key={i} />;
default:
return null;
}
})}

价值(面试讲法):

  • 可交互:用户能在 AI 回答里直接点按钮、填表单、确认操作(human-in-the-loop)。
  • 信息密度高:表格/图表/卡片比一大段文字更易读。
  • 可控安全:组件由你写死,避免模型直接输出 HTML 带来的 XSS 风险(对照 Markdown 协议)。

Prompt 可视化与可配置:把“魔法”变成“产品能力”

不要把 Prompt 写死在代码里(尤其是产品化后),建议提供:

  • Prompt 模板选择:不同任务不同模板
  • 关键参数可调:语气、长度、是否要引用、输出格式
  • 版本管理:改 Prompt 就像改配置,能回滚

用户体验层可以这样设计:

  • “高级设置”抽屉:展示关键参数
  • “提示词预览”只读区域:让用户知道系统在做什么(增强信任)

最小可视化组件清单

  • 模型选择器(主/备模型)
  • 稳定性滑杆(温度映射)
  • 输出长度选项(短/中/长)
  • 是否引用来源(RAG 开关)

最小可运行 Demo(体验版)

目标:做出一个“能流式+能停止+有失败兜底”的最小交互体验

最小可运行代码(UI 状态机 + 取消)

type State = "idle" | "streaming" | "stopped" | "failed" | "done";
let state: State = "idle";

function send() {
state = "streaming";
// start stream...
}

function stop() {
// abort stream...
state = "stopped";
}

function retry() {
state = "streaming";
}

完整 Demo 结构(最小体验骨架)

demo-ai-ux/
src/
App.tsx
ChatMessage.tsx
useStream.ts
uiStates.ts

常见坑排查清单

  • 状态错乱:多次点击“发送/停止”无防抖 → 加按钮禁用/节流
  • Markdown 卡顿:流式阶段先纯文本,结束后再渲染
  • 兜底文案缺失:只显示“错误” → 加可操作提示

完整可运行代码(流式体验示例)

源码目录:docs/demos/ai-chat-demo/web

cd docs/demos/ai-chat-demo/web
npm i
npm run dev